package cc.seiya.redis.client;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * redis    distributed     lock
 *
 * @author libo
 * @date 2017/3/3 13:27
 */
public class RedisLock {

    private final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;

    /**
     * 锁超时 时间  多少毫秒
     */
    private int lockExpire = 10 * 1000;

    private int lockTimeout = 5 * 1000;


    /**
     * redis lock key
     */
    private String lockKey = "lock:key";

    /**
     * is locked
     */
    private volatile boolean locked = false;

    /**
     * redis client
     */
    private RedisClient redisClient = new RedisClient();

    public RedisLock(String lockKey) {
        this.lockKey = lockKey;
    }

    public RedisLock(String lockKey, int lockTimeout, int lockExpire) {
        this.lockKey = lockKey;
        this.lockTimeout = lockTimeout;
        this.lockExpire = lockExpire;
    }

    /**
     * 加锁,分布式锁
     *
     * @return
     * @throws InterruptedException
     * @date 2017-03-03 14:09:30
     * @author libo
     */
    public boolean lock() throws InterruptedException {
        int timeout = this.lockTimeout;
        Random random = new Random();
        while (timeout >= 0) {
            //过期时间
            Long expire = System.currentTimeMillis() + lockExpire + 1;
            String expireStr = String.valueOf(expire);
            //加锁成功直接返回
            if (redisClient.setNX(lockKey, expireStr)) {
                redisClient.expire(lockKey, lockExpire / 1000);
                this.locked = true;
                return locked;
            }
            //获取当前 锁的 value
            String currentLockedValueStr = this.redisClient.get(lockKey);

            //判断是否为空，不为空的情况下，如果被其他线程设置了值，则第二个条件判断是过不去的
            if (currentLockedValueStr != null && (currentLockedValueStr.trim().length() > 0) && Long.valueOf
                    (currentLockedValueStr) < System.currentTimeMillis()) {

                //获取上一个锁到期时间，并设置现在的锁到期时间，
                //只有一个线程才能获取上一个线上的设置时间，因为jedis.getSet是同步的
                String oldLockedValue = this.redisClient.getSet(lockKey, expireStr);

                //防止误删（覆盖，因为key是相同的）了他人的锁——这里达不到效果，这里值会被覆盖，但是因为什么相差了很少的时间，所以可以接受
                //[分布式的情况下]:如过这个时候，多个线程恰好都到了这里，但是只有一个线程的设置值和当前值相同，他才有权利获取锁
                if (oldLockedValue != null && oldLockedValue.equals(currentLockedValueStr)) {
                    locked = true;
                    return locked;
                }
            }
            timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
            /*
                延迟100 毫秒,  这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程,
                只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,
                这将可能导致前面来的锁得不到满足.
                使用随机的等待时间可以一定程度上保证公平性
             */
            TimeUnit.MILLISECONDS.sleep(random.nextInt(10));

        }
        return false;
    }

    /**
     * 解锁
     *
     * @date 2017-03-03 14:09:15
     * @author libo
     */
    public void unlock() {
        if (locked) {
            redisClient.del(lockKey);
            locked = false;
        }

    }

}
